package com.github.soiff.ip;

import java.io.PrintStream;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by z0228 on 2017/1/1.
 * An implementation for TODO:
 *
 * @author : z0228
 * @version : 1.0
 * @since : 1.8
 */
public class IpAddresses {

    enum IpType {
        ipv4, ipv6, unknown
    }

    enum AddressType {
        anylocal, linklocal, loopback, multicast, sitelocal,
        mcglobal, mclinklocal, mcnodelocal, mcorglocal, mcsitelocal
    }

    enum InterfaceType {
        loopback, p2p, virtual, normal, external
    }

    private static final Map<InterfaceType, Map<AddressType, Map<IpType, List<InetAddress>>>> MAP =
        new HashMap<>();

    private static void collect(InterfaceType it, AddressType type, InetAddress address) {
        if (!MAP.containsKey(it))
            MAP.put(it, new HashMap<>());
        IpType ip = IpType.unknown;
        if (address instanceof Inet4Address)
            ip = IpType.ipv4;
        else if (address instanceof Inet6Address)
            ip = IpType.ipv6;
        if (!MAP.get(it).containsKey(type))
            MAP.get(it).put(type, new HashMap<>());
        if (!MAP.get(it).get(type).containsKey(ip))
            MAP.get(it).get(type).put(ip, new ArrayList<>());
        MAP.get(it).get(type).get(ip).add(address);
    }

    private static void display(PrintStream ps) {
        final List<InterfaceType> interfaceTypes = Arrays.asList(InterfaceType.external, InterfaceType.normal);
        final List<AddressType> addressTypes = Collections.singletonList(AddressType.sitelocal);
        final List<IpType> ipTypes = Arrays.asList(IpType.ipv4, IpType.ipv6);
        for (InterfaceType it : interfaceTypes) {
            for (AddressType at : addressTypes) {
                for (IpType ipType : ipTypes) {
                    if (MAP.containsKey(it) && MAP.get(it).containsKey(at) && MAP.get(it).get(at).containsKey(ipType) &&
                        MAP.get(it).get(at).get(ipType).size() > 0) {
                        ps.println(MAP.get(it).get(at).get(ipType).get(0).getHostAddress());
                        return;
                    }
                }
            }
        }
    }

    private static boolean available(InetAddress inetAddress) {
        try {
            new Socket("www.baidu.com", 80, inetAddress, 0);
            return true;
        } catch (Throwable ignored) {
        }
        return false;
    }

    private static void filter() {
        if (MAP.containsKey(InterfaceType.normal))
            if (MAP.get(InterfaceType.normal).containsKey(AddressType.sitelocal)) {
                if (MAP.get(InterfaceType.normal).get(AddressType.sitelocal).containsKey(IpType.ipv4)) {
                    MAP.get(InterfaceType.normal).get(AddressType.sitelocal).get(IpType.ipv4).forEach(ipv4 -> {
                        if (available(ipv4)) {
                            collect(InterfaceType.external, AddressType.sitelocal, ipv4);
                        }
                    });
                }
                if (MAP.get(InterfaceType.normal).get(AddressType.sitelocal).containsKey(IpType.ipv6)) {
                    MAP.get(InterfaceType.normal).get(AddressType.sitelocal).get(IpType.ipv6).forEach(ipv6 -> {
                        if (available(ipv6)) {
                            collect(InterfaceType.external, AddressType.sitelocal, ipv6);
                        }
                    });
                }
            }
    }

    public static void main(String[] args) throws SocketException {
        Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
        Enumeration<InetAddress> addresses;
        NetworkInterface ni;
        InetAddress address;
        InterfaceType type;
        while (interfaces.hasMoreElements()) {
            ni = interfaces.nextElement();
            if (!ni.isUp())
                continue;
            if (ni.isLoopback()) {
                type = InterfaceType.loopback;
            } else if (ni.isPointToPoint()) {
                type = InterfaceType.p2p;
            } else if (ni.isVirtual()) {
                type = InterfaceType.virtual;
            } else {
                type = InterfaceType.normal;
            }
            addresses = ni.getInetAddresses();
            while (addresses.hasMoreElements()) {
                address = addresses.nextElement();
                if (address.isAnyLocalAddress()) collect(type, AddressType.anylocal, address);
                if (address.isLinkLocalAddress()) collect(type, AddressType.linklocal, address);
                if (address.isLoopbackAddress()) collect(type, AddressType.loopback, address);
                if (address.isMCGlobal()) collect(type, AddressType.mcglobal, address);
                if (address.isMCLinkLocal()) collect(type, AddressType.mclinklocal, address);
                if (address.isMCNodeLocal()) collect(type, AddressType.mcnodelocal, address);
                if (address.isMCOrgLocal()) collect(type, AddressType.mcorglocal, address);
                if (address.isMCSiteLocal()) collect(type, AddressType.mcsitelocal, address);
                if (address.isMulticastAddress()) collect(type, AddressType.multicast, address);
                if (address.isSiteLocalAddress()) collect(type, AddressType.sitelocal, address);
            }
        }
        filter();
        display(System.out);
    }
}
